unit traitlist;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, StdCtrls, Menus, database, fpjson,
  LCLTranslator;

type

  { TTraitListFrame }

  TTraitListFrame = class(TFrame)
    EditMenuItem: TMenuItem;
    CutMenuItem: TMenuItem;
    CopyMenuItem: TMenuItem;
    DeleteMenuItem: TMenuItem;
    SelectAllTraitsMenuItem: TMenuItem;
    PasteMenuItem: TMenuItem;
    TraitPopupMenu: TPopupMenu;
    TraitsListBox: TListBox;
    procedure CopyMenuItemClick(Sender: TObject);
    procedure CutMenuItemClick(Sender: TObject);
    procedure DeleteMenuItemClick(Sender: TObject);
    procedure EditMenuItemClick(Sender: TObject);
    procedure PasteMenuItemClick(Sender: TObject);
    procedure SelectAllTraitsMenuItemClick(Sender: TObject);
    procedure TraitPopupMenuPopup(Sender: TObject);
    procedure TraitsListBoxDblClick(Sender: TObject);
    procedure TraitsListBoxKeyUp(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure TraitsListBoxMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    { This is handled never disposed, handled by outer object. }
    Db: TDatabase;
    { This is disposed by the JSON object in the database, it's NOT handled
      by TTraitListFrame }
    TraitList: TJSONArray;
    procedure AddNewTrait;
    procedure EditSelectedTrait;
    procedure ShowPopupMenu;
    function IsRealTraitSelected: Boolean;
    procedure CopySelectedToClipboard;
    procedure DeleteSelectedTraits;
  public
    { This should be called before any other function below. }
    procedure SetDatabase(ADb: TDatabase);
    procedure SetTraitList(ATraitList: TJSONArray);
    procedure UpdateTraits;
    function TraitToText(Trait: TJSONObject): String;
  end;

resourcestring
  EditTraitMenuItem = 'Edit';
  AddNewTraitMenuItem = 'Add new trait';
  AddNewTraitStr = '<Add new trait>';

  TraitElementRate = 'Element rate: ';
  TraitDebuffRate = 'Debuff rate: ';
  TraitStateRate = 'State rate: ';
  TraitStateResist = 'State resist: ';
  TraitParameter = 'Parameter: ';
  TraitExParameter = 'ExParameter: ';
  TraitSpParameter = 'SpParameter: ';
  TraitAttackElement = 'Attack element: ';
  TraitAttackState = 'Attack state: ';
  TraitAttackSpeed = 'Attack speed: ';
  TraitAttacksPerTurn = 'Attacks per turn: ';
  TraitAddSkillType = 'Add skill type: ';
  TraitSealSkillType = 'Seal skill type: ';
  TraitAddSkill = 'Add skill: ';
  TraitSealSkill = 'Seal skill: ';
  TraitWeaponType = 'Weapon type: ';
  TraitArmourType = 'Armour type: ';
  TraitLockEquipTypes = 'Lock equip type: ';
  TraitSealEquipTypes = 'Seal equip type: ';
  TraitSlotType2Weapons = 'Slot type: Two weapons';
  TraitSlotTypeStandard = 'Slot type: Standard';
  TraitDoubleActionChance = 'Double action chance: ';
  TraitSpecialFlag = 'Special: ';
  TraitKnockoutEffect = 'Knockout effect: ';
  TraitPartyEffect = 'Party trait: ';
  TraitUnknownTrait = '(Unknown trait)';

implementation

uses
  Dialogs, traitselector, LCLType, clipboardhelper, listboxhelper;

{$R *.lfm}

procedure TTraitListFrame.TraitsListBoxDblClick(Sender: TObject);
begin
  if not IsRealTraitSelected then
    AddNewTrait
  else
    EditSelectedTrait
end;

procedure TTraitListFrame.EditMenuItemClick(Sender: TObject);
begin
  if not IsRealTraitSelected then
    AddNewTrait
  else
    EditSelectedTrait
end;

procedure TTraitListFrame.PasteMenuItemClick(Sender: TObject);
var
  PastedArray: TJSONArray;
  I: Integer;
  StartId: Integer;
begin
  StartId := TraitsListBox.ItemIndex;

  PastedArray := GetJsonArrayFromClipboard('application/rpgmv-traits');
  if PastedArray = nil then
    Exit;

  try
    for I := 0 to PastedArray.Count -1 do begin
      if PastedArray[I].JSONType = jtObject then begin
        TraitList.Insert(StartId + I, PastedArray.Objects[I].Clone as TJSONObject);
      end;
    end;
  finally
    PastedArray.Free;
  end;

  UpdateTraits;
end;

procedure TTraitListFrame.SelectAllTraitsMenuItemClick(Sender: TObject);
begin
  TraitsListBox.SelectAll;
  TraitsListBox.Selected[TraitsListBox.Items.Count - 1] := False;
end;

procedure TTraitListFrame.TraitPopupMenuPopup(Sender: TObject);
begin
  PasteMenuItem.Enabled := ClipboardHasData('application/rpgmv-traits');
end;

procedure TTraitListFrame.DeleteMenuItemClick(Sender: TObject);
begin
  DeleteSelectedTraits
end;

procedure TTraitListFrame.CopyMenuItemClick(Sender: TObject);
begin
  CopySelectedToClipboard
end;

procedure TTraitListFrame.CutMenuItemClick(Sender: TObject);
begin
  CopySelectedToClipboard;
  DeleteSelectedTraits
end;

procedure TTraitListFrame.TraitsListBoxKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_DELETE then
    DeleteSelectedTraits;

  if Key = VK_RETURN then
    TraitsListBoxDblClick(Sender);
end;

procedure TTraitListFrame.TraitsListBoxMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbRight then begin
    ShowPopupMenu;
  end;
end;

procedure TTraitListFrame.ShowPopupMenu;
var
  IsForRealTrait: Boolean;
begin
  IsForRealTrait := IsRealTraitSelected;

  DeleteMenuItem.Enabled := ListboxSelectionNotEmpty(TraitsListBox);

  if IsForRealTrait then
    EditMenuItem.Caption := EditTraitMenuItem
  else
    EditMenuItem.Caption := AddNewTraitMenuItem;

  TraitPopupMenu.PopUp;
end;

function TTraitListFrame.IsRealTraitSelected: Boolean;
var
  LastIsSelected, NoneSelected: Boolean;
begin
  LastIsSelected := TraitsListBox.ItemIndex = TraitsListBox.Items.Count - 1;
  NoneSelected := TraitsListBox.ItemIndex < 0;

  IsRealTraitSelected := not LastIsSelected and not NoneSelected;
end;

procedure TTraitListFrame.CopySelectedToClipboard;
var
  ArrSrl: TClipboardArraySerializer;
  I, NumCopied: Integer;
begin
  ArrSrl := TClipboardArraySerializer.Create(DataTypeIds[DATA_TYPE_ID_TRAITS]);
  try
    NumCopied := 0;
    for I := 0 to TraitsListBox.Count -1 do begin
      if TraitsListBox.Selected[I] then begin
        ArrSrl.AddItem(TraitList.Objects[I]);
        Inc(NumCopied)
      end
    end;

    if NumCopied > 0 then
      ArrSrl.CopyToClipboard
  finally
    ArrSrl.Free;
  end;
end;

procedure TTraitListFrame.DeleteSelectedTraits;
var
  I: Integer;
begin
  for I := TraitsListBox.Count -1 downto 0 do begin
    if TraitsListBox.Selected[I] then begin
      TraitList.Delete(I);
    end
  end;

  UpdateTraits
end;

procedure TTraitListFrame.AddNewTrait;
var
  NewTrait: TJSONObject;
begin
  if TraitSelectorForm.ShowTraitSelection(Db) then begin
    NewTrait := TJSONObject.Create;
    NewTrait.Add('code', TraitSelectorForm.SelectedCode);
    NewTrait.Add('dataId', TraitSelectorForm.SelectedDataId);
    NewTrait.Add('value', TraitSelectorForm.SelectedValue);

    TraitList.Add(NewTrait);

    TraitsListBox.Items.BeginUpdate;
    TraitsListBox.Items[TraitsListBox.Items.Count - 1] := TraitToText(NewTrait);
    TraitsListBox.Items.Add(AddNewTraitStr);
    TraitsListBox.Items.EndUpdate;
  end;
end;

procedure TTraitListFrame.EditSelectedTrait;
var
  Trait: TJSONObject;
  CodeJson, DataIdJson, ValueJson: TJSONNumber;
  Code: Integer = 0;
  DataId: Integer = 0;
  Value: Double = 0.0;
begin
  Trait := TraitList.Objects[TraitsListBox.ItemIndex];
  if Trait.Find('code', CodeJson) then
    Code := CodeJson.AsInteger;
  if Trait.Find('dataId', DataIdJson) then
    DataId := DataIdJson.AsInteger;
  if Trait.Find('value', ValueJson) then
    Value := ValueJson.AsFloat;

  if TraitSelectorForm.ShowTraitSelection(Db, Code, DataId, Value) then begin
    Trait.Integers['code'] := TraitSelectorForm.SelectedCode;
    Trait.Integers['dataId'] := TraitSelectorForm.SelectedDataId;
    Trait.Floats['value'] := TraitSelectorForm.SelectedValue;
    TraitsListBox.Items[TraitsListBox.ItemIndex] := TraitToText(Trait);
  end
end;

procedure TTraitListFrame.SetDatabase(ADb: TDatabase);
begin
  Db := ADb;
end;

procedure TTraitListFrame.SetTraitList(ATraitList: TJSONArray);
begin
  TraitList := ATraitList;
  UpdateTraits;
end;

procedure TTraitListFrame.UpdateTraits;
var
  I: Integer;
begin
  Assert(Db <> nil, 'Database must be set before setting trait list!');

  TraitsListBox.Items.BeginUpdate;
  TraitsListBox.Clear;
  if TraitList <> nil then begin
    for I := 0 to TraitList.Count -1 do begin
      TraitsListBox.Items.Add(TraitToText(TraitList.Objects[I]));
    end;
  end;
  TraitsListBox.Items.Add(AddNewTraitStr);
  TraitsListBox.Items.EndUpdate;
end;

function TTraitListFrame.TraitToText(Trait: TJSONObject): String;
var
  CodeJson, DataIdJson, ValueJson: TJSONNumber;
  Code: Integer = 0;
  DataId: Integer = 0;
  Value: Double = 0.0;

  function FormatPlusMinusValue(Val: Double): String;
  begin
    if Val < 0 then
      FormatPlusMinusValue := '- ' + FloatToStr(Abs(Val))
    else
      FormatPlusMinusValue := '+ ' + FloatToStr(Val)
  end;

begin
  Assert(Db <> nil, 'Database must be set before setting trait list!');

  if Trait.Find('code', CodeJson) then
    Code := CodeJson.AsInteger;

  if Trait.Find('dataId', DataIdJson) then
    DataId := DataIdJson.AsInteger;

  if Trait.Find('value', ValueJson) then
    Value := ValueJson.AsFloat;

  case code of
  TRAIT_ELEMENT_RATE:
    TraitToText := TraitElementRate
                + Db.GetElementName(DataId)
                + ' * ' + FloatToStr(Value * 100) + '%';
  TRAIT_DEBUFF_RATE:
    TraitToText := TraitDebuffRate
                + Db.GetParamName(DataId)
                + ' * ' + FloatToStr(Value * 100) + '%';
  TRAIT_STATE_RATE:
    TraitToText := TraitStateRate
                + Db.GetStateName(DataId)
                + ' * ' + FloatToStr(Value * 100) + '%';
  TRAIT_STATE_RESIST:
    TraitToText := TraitStateResist
                + Db.GetStateName(DataId);

  TRAIT_PARAM:
    TraitToText := TraitParameter
                + Db.GetParamName(DataId)
                + ' * ' + FloatToStr(Value * 100) + '%';

  TRAIT_XPARAM:
    TraitToText := TraitExParameter
                + Db.GetExParamName(DataId)
                + ' ' + FormatPlusMinusValue(Value * 100) + '%';

  TRAIT_SPARAM:
    TraitToText := TraitSpParameter
                + Db.GetSpParamName(DataId)
                + ' * ' + FloatToStr(Value * 100) + '%';

  TRAIT_ATTACK_ELEMENT:
    TraitToText := TraitAttackElement
                + Db.GetElementName(DataId);

  TRAIT_ATTACK_STATE:
    TraitToText := TraitAttackState
                + Db.GetStateName(DataId)
                + ' + ' + FloatToStr(Value * 100) + '%';

  TRAIT_ATTACK_SPEED:
    TraitToText := TraitAttackSpeed
                + FormatPlusMinusValue(Value);

  TRAIT_ATTACK_TIMES:
    TraitToText := TraitAttacksPerTurn
                + FormatPlusMinusValue(Value);


  TRAIT_STYPE_ADD:
    TraitToText := TraitAddSkillType
                + Db.GetSkillTypeName(DataId);

  TRAIT_STYPE_SEAL:
    TraitToText := TraitSealSkillType
                + Db.GetSkillTypeName(DataId);

  TRAIT_SKILL_ADD:
    TraitToText := TraitAddSkill
                + Db.GetSkillName(DataId);

  TRAIT_SKILL_SEAL:
    TraitToText := TraitSealSkill
                + Db.GetSkillName(DataId);

  TRAIT_EQUIP_WTYPE:
    TraitToText := TraitWeaponType
                + Db.GetWeaponTypeName(DataId);

  TRAIT_EQUIP_ATYPE:
    TraitToText := TraitArmourType
                + Db.GetArmourTypeName(DataId);

  TRAIT_EQUIP_LOCK:
    TraitToText := TraitLockEquipTypes
                + Db.GetEquipTypeName(DataId);

  TRAIT_EQUIP_SEAL:
    TraitToText := TraitSealEquipTypes
                + Db.GetEquipTypeName(DataId);

  TRAIT_SLOT_TYPE:
    if Value = 1.0 then
        TraitToText := TraitSlotType2Weapons
    else
      TraitToText := TraitSlotTypeStandard;

  TRAIT_ACTION_PLUS:
      TraitToText := TraitDoubleActionChance
                  + ' + ' + FloatToStr(Value * 100) + '%';

  TRAIT_SPECIAL_FLAG:
    TraitToText := TraitSpecialFlag
                + Db.GetSpecialFlagName(DataId);

  TRAIT_COLLAPSE_TYPE:
    TraitToText := TraitKnockoutEffect
                + Db.GetKnockoutEffectName(DataId);

  TRAIT_PARTY_ABILITY:
    TraitToText := TraitPartyEffect
                + Db.GetPartyFlagName(DataId);
  else
    TraitToText := TraitUnknownTrait;
  end;
end;


end.

